package com.emeraldparser.symbol;

import com.emeraldparser.util.Automaton;
import com.emeraldparser.util.Automatons;
import com.emeraldparser.util.Characters;
import com.emeraldparser.util.HashCodes;
import com.google.common.base.Objects;
import com.google.common.base.Preconditions;

public final class CharSequenceSymbolClass extends AbstractSymbolClass {

	private final CharSequence charSequence;
	private final transient int hashCode;

	public CharSequenceSymbolClass(CharSequence charSequence) {
		this.charSequence = Preconditions.checkNotNull(charSequence, "Character sequence must not be null");
		this.hashCode = HashCodes.classSpecificHashCode(CharSequenceSymbolClass.class, charSequence);
	}

	public CharSequence getCharSequence() {
		return charSequence;
	}

	@Override
	public Automaton<? extends State, ? extends SymbolClass> toAutomaton() {
		Automaton<State, SymbolClass> automaton = Automatons.create(super.toAutomaton());
		
		IndexedState currentState = new IndexedState(0);
		automaton.addVertex(currentState);
		automaton.addStartVertex(currentState);
		
		for (int index = 0; index < charSequence.length(); index++) {
			char character = charSequence.charAt(index);
			
			IndexedState nextState = new IndexedState(index + 1);
			automaton.addVertex(nextState);
			
			automaton.addDirectedEdge(currentState, nextState, new CharacterSymbolClass(character));
			
			currentState = nextState;
		}
		
		automaton.addAcceptVertex(currentState);
		
		return automaton;
	}

	@Override
	public String toString() {
		return "\"" + Characters.escape(charSequence) + "\"";
	}

	@Override
	public int hashCode() {
		return hashCode;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj) {
			return true;
		}
		
		if (obj == null) {
			return false;
		}
		
		if (getClass() != obj.getClass()) {
			return false;
		}
		
		CharSequenceSymbolClass other = (CharSequenceSymbolClass) obj;
		
		return Objects.equal(charSequence, other.charSequence);
	}

	private class IndexedState implements SymbolClass.State {
		
		private final int index;
		private final transient int hashCode;
		
		IndexedState(int index) {
			this.index = index;
			this.hashCode = Objects.hashCode(CharSequenceSymbolClass.this, index);
		}
		
		@Override
		public String toString() {
			StringBuilder text = new StringBuilder();
			
			text.append("\"");
			
			for (int index = 0; index < charSequence.length(); index++) {
				char character = charSequence.charAt(index);
				
				if (index == this.index) {
					text.append(".");
				}
				
				text.append(Characters.escape(character));
			}
			
			if (index == charSequence.length()) {
				text.append(".");
			}
			
			text.append("\"");
			
			return text.toString();
		}

		@Override
		public int hashCode() {
			return hashCode;
		}

		@Override
		public boolean equals(Object obj) {
			if (this == obj) {
				return true;
			}
			
			if (obj == null) {
				return false;
			}
			
			if (getClass() != obj.getClass()) {
				return false;
			}
			
			IndexedState other = (IndexedState) obj;
			
			return Objects.equal(getCharSequenceSymbolClass(), other.getCharSequenceSymbolClass())
			    && index == other.index;
		}

		private CharSequenceSymbolClass getCharSequenceSymbolClass() {
			return CharSequenceSymbolClass.this;
		}

	}

	@Override
	public <I, O> O accept(SymbolClassVisitor<I, O> visitor, I input) {
		return visitor.visitCharSequenceSymbolClass(this, input);
	}

}
